package model;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Server {
    public Server[] neighbors = new Server[Model.S];
    public String name;
    public Set<Server> serversSeen = new HashSet<Server>();
    public Map<String, Server[]> miniMeshes = new HashMap<String, Server[]>();
    public int clientsAttached = 0;
    public Map<String, Integer> clientsPerMesh = new HashMap<String, Integer>();
    public List<String> producersAttached = new LinkedList<String>();

    public Server getLeastLoadedNeighbor() {
        int min = Model.SLOTS + 1;
        Server ret = this;
        for (Server s : Model.bfOrder(this)) {
            if (s.clientsAttached == 0) {
                return s;
            }
            if (s.clientsAttached < min) {
                min = s.clientsAttached;
                ret = s;
            }
        }
        return ret;
    }

    public Server getNeighborInMesh(String meshName) {
        int min = Model.SLOTS + 1;
        Server ret = this;
        for (Server s : Model.bfOrder(this)) {
            if (s.miniMeshes.containsKey(meshName) && s.clientsAttached< Model.SLOTS) {
                return s;
            }
            if (s.clientsAttached < min) {
                min = s.clientsAttached;
                ret = s;
            }
        }
        return ret;
    }

    public boolean addNeighbor(Server s) {
        if (s.name.equals(this.name))
            return false;

        serversSeen.add(s);
        if (serversSeen.size() <= Model.S) {
            neighbors[serversSeen.size() - 1] = s;
            return true;
        } else {
            int t = Model.RAND.nextInt(serversSeen.size());
            if (t < Model.S) {
                neighbors[t] = s;
                return true;
            }
        }
        return false;
    }

    private static int getNullIndex(Server[] servs) {
        for (int i = 0; i < servs.length; i++) {
            if (servs[i] == null) {
                return i;
            }
        }
        return -1;
    }

    public boolean addMiniMeshNeighbor(Server s, String meshName) {
        if (s.name.equals(this.name))
            return false;
        Server[] miniMeshNeighbors = miniMeshes.get(meshName);
        if (miniMeshNeighbors == null) {
            // this server is not in the mesh and should ignore the add
            return false;
        }
        int nullIndex = getNullIndex(miniMeshNeighbors);
        if (nullIndex >= 0) {
            miniMeshNeighbors[nullIndex] = s;
            return true;
        } else {
            int t = Model.RAND.nextInt(serversSeen.size());
            if (t < Model.S) {
                miniMeshNeighbors[t] = s;
                return true;
            }
        }
        return false;
    }

    public boolean hasNeighbors() {
        return neighbors[0] != null;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Server " + name + "  neighbors ");
        for (Server s : neighbors) {
            if (s != null) {
                sb.append(s.name);
                sb.append(" ");
            }
        }
        return sb.toString();
    }

}
